//
// Created by yunnan xu on 2020/12/15.
//

#include "BlinnPhongLightPass.h"
#include "Scene.h"
#include "EngineCore.h"
#include "Camera.h"
#include "RenderItem.h"
#include "RenderItemStaticOpaque.h"
#include "RenderItemSkinnedMeshOpaque.h"
#include "Model.h"
#include "Texture.h"
#include "SkinnedMeshModel.h"
#include <sstream>

BlinnPhongLightPass<GraphicsAPI::GLESv3>::BlinnPhongLightPass(bool _with_skinned_mesh) : with_skinned_mesh(_with_skinned_mesh)
{
}

void BlinnPhongLightPass<GraphicsAPI::GLESv3>::InitShaderUniformLocation()
{
    /*
     *      GLint CameraVPMatrix_location;
            GLint ModelMatrix_location;
            GLint LightProjMatrix_location;
            GLint shadowMap_location;
            GLint lightPos_location;
            GLint cameraPos_location;

            SkinnedMesh:
                GLint inBoneIndex_location;
                GLint inBoneWeights_location;
                GLint BoneTransformList_location;

     * */

    GL_CHECK(glUseProgram(program));
    CameraVPMatrix_location = GL_CHECK(glGetUniformLocation(program, "CameraVPMatrix"));
    ModelMatrix_location = GL_CHECK(glGetUniformLocation(program, "ModelMatrix"));
    LightProjMatrix_location = GL_CHECK(glGetUniformLocation(program, "LightProjMatrix"));
    shadowMap_location = GL_CHECK(glGetUniformLocation(program, "shadowMap"));
    lightPos_location = GL_CHECK(glGetUniformLocation(program, "lightPos"));
    cameraPos_location = GL_CHECK(glGetUniformLocation(program, "cameraPos"));

    if (with_skinned_mesh)
    {
        // inBoneIndex_location = GL_CHECK(glGetUniformLocation(program, "inBoneIndex"));
        // inBoneWeights_location = GL_CHECK(glGetUniformLocation(program, "inBoneWeights"));
        BoneTransformList_location = GL_CHECK(glGetUniformLocation(program, "BoneTransformList"));
    }

    LOGI("CameraVPMatrix_location = %d, ModelMatrix_location = %d, LightProjMatrix_location = %d, shadowMap_location = %d, lightPos_location = %d, cameraPos_location = %d",
            CameraVPMatrix_location, ModelMatrix_location, LightProjMatrix_location, shadowMap_location, lightPos_location, cameraPos_location);

    assert(CameraVPMatrix_location >= 0);
    assert(ModelMatrix_location >= 0);
    assert(LightProjMatrix_location >= 0);
    assert(shadowMap_location >= 0);
    assert(lightPos_location >= 0);
    assert(cameraPos_location >= 0);

    if (with_skinned_mesh)
    {
        LOGI("BoneTransformList_location = %d", BoneTransformList_location);

        // assert(inBoneIndex_location >= 0);
        // assert(inBoneWeights_location >= 0);
        // assert(BoneTransformList_location >= 0);
    }

    glUniform1i(shadowMap_location, 1);
}

void BlinnPhongLightPass<GraphicsAPI::GLESv3>::Draw()
{
    GL_CHECK(glUseProgram(program));
    auto curr_scene = SceneMgr::GetInstance()->GetCurrentScene();
    auto main_cam = curr_scene->GetMainCamera();

    glm::vec3 light_pos = curr_scene->GetLightPos(0);
    GL_CHECK(glUniform3fv(lightPos_location, 1, glm::value_ptr(light_pos)));

    glm::vec3 camera_pos = main_cam->GetPosition();
    GL_CHECK(glUniform3fv(cameraPos_location, 1, glm::value_ptr(camera_pos)));

    glm::mat4 light_matrix = curr_scene->GetLightMatrix(0);
    glm::mat4 biasMatrix(
            0.5, 0.0, 0.0, 0.0,
            0.0, 0.5, 0.0, 0.0,
            0.0, 0.0, 0.5, 0.0,
            0.5, 0.5, 0.5, 1.0
    );
    light_matrix = biasMatrix * light_matrix;
    GLfloat * light_matrix_value = glm::value_ptr(light_matrix);
    GL_CHECK(glUniformMatrix4fv(LightProjMatrix_location, 1, false, light_matrix_value));

    auto vp_matrix = GL_CHECK(main_cam->GetProjMatrix() * main_cam->GetViewMatrix());
    auto camera_vp_matrix = vp_matrix;
    GLfloat * camera_matrix_value = glm::value_ptr(camera_vp_matrix);
    GL_CHECK(glUniformMatrix4fv(CameraVPMatrix_location, 1, false, camera_matrix_value));


    if (!with_skinned_mesh) {

        for (auto iter = curr_scene->GetOpaqueItemBegin(); iter != curr_scene->GetOpaqueItemEnd(); ++iter) {
            auto gles_item = std::dynamic_pointer_cast<RenderItemStaticOpaque<GraphicsAPI::GLESv3>>(*iter);
            auto model_mat = gles_item->GetModel()->GetModelMatrix();
            GLfloat *model_matrix_value = glm::value_ptr(model_mat);
            GL_CHECK(glUniformMatrix4fv(ModelMatrix_location, 1, false, model_matrix_value));
            GL_CHECK(glBindVertexArray(gles_item->GetVAO()));
            std::shared_ptr<Texture2D<GraphicsAPI::GLESv3>> tex = std::dynamic_pointer_cast<Texture2D<GraphicsAPI::GLESv3>>(
                    gles_item->GetTexture(0));
            GL_CHECK(glActiveTexture(tex->GetTextureUnit()));
            GL_CHECK(glBindTexture(GL_TEXTURE_2D, tex->GetTexture()));
            GL_CHECK(glDrawElements(GL_TRIANGLES, gles_item->GetModel()->GetFaceNumber() * 3,
                                    gles_item->GetModel()->GetIndexType(), 0));
            GL_CHECK(glBindVertexArray(0));
        }
    }
    else {

        for (auto iter = curr_scene->GetSkinnedMeshItemBegin(); iter != curr_scene->GetSkinnedMeshItemEnd(); ++iter) {

            auto gles_item = std::dynamic_pointer_cast<RenderItemSkinnedMeshOpaque<GraphicsAPI::GLESv3>>(*iter);
            auto model_mat = gles_item->GetModel()->GetModelMatrix();
            GLfloat *model_matrix_value = glm::value_ptr(model_mat);

            GL_CHECK(glUniformMatrix4fv(ModelMatrix_location, 1, false, model_matrix_value));

            auto skinned_mesh_model = std::dynamic_pointer_cast<SkinnedMeshModel>(gles_item->GetModel());
            int bone_number = skinned_mesh_model->GetBoneNumber();
            float * bone_trans_mat = skinned_mesh_model->GetBoneTransformMatrixList();
            GL_CHECK(glUniformMatrix4fv(BoneTransformList_location, bone_number, true, bone_trans_mat));

            GL_CHECK(glBindVertexArray(gles_item->GetVAO()));

            std::shared_ptr<Texture2D<GraphicsAPI::GLESv3>> tex = std::dynamic_pointer_cast<Texture2D<GraphicsAPI::GLESv3>>(
                    gles_item->GetTexture(0));
            GL_CHECK(glActiveTexture(tex->GetTextureUnit()));
            GL_CHECK(glBindTexture(GL_TEXTURE_2D, tex->GetTexture()));
            GL_CHECK(glDrawElements(GL_TRIANGLES, gles_item->GetModel()->GetFaceNumber() * 3, gles_item->GetModel()->GetIndexType(), 0));
            GL_CHECK(glBindVertexArray(0));
        }
    }

}